package pl.umk.mat.fred.unicore.urc.storagemanagement.analytics;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import javax.security.auth.x500.X500Principal;

import org.apache.commons.httpclient.util.URIUtil;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PlatformUI;
import org.unigrids.services.atomic.types.ProtocolType;
import org.w3.x2005.x08.addressing.EndpointReferenceType;

import pl.umk.mat.fred.unicore.urc.storagemanagement.StorageManagementActivator;
import pl.umk.mat.fred.unicore.urc.storagemanagement.analytics.MonitoredStreams.MonitoredInputStream;
import pl.umk.mat.fred.unicore.urc.storagemanagement.analytics.MonitoredStreams.MonitoredOutputStream;
import pl.umk.mat.fred.unicore.urc.storagemanagement.preferences.PreferenceConstants;
import pl.umk.mat.fred.unicore.urc.storagemanagement.tools.StorageTools;
import pl.umk.mat.fred.unicore.urc.storagemanagement.unicore.Storage;
import pl.umk.mat.fred.unicore.urc.storagemanagement.views.TimeMeterView;
import de.fzj.unicore.rcp.identity.IdentityActivator;
import de.fzj.unicore.uas.client.FileTransferClient;
import de.fzj.unicore.uas.client.StorageClient;
import de.fzj.unicore.uas.client.TransferControllerClient;
import de.fzj.unicore.uas.security.IUASSecurityProperties;
import de.fzj.unicore.wsrflite.xmlbeans.BaseFault;

public class TimeMeter implements Runnable {

	private static final String dirName = ".time_meter_dir";
	
	private TimeMeterView view;
	private List<Storage> storageList;
	private List<Storage> myStorageList;
	private Map<String, List<Integer>> speedMap;
	private int TEST_FILE_SIZE;
	private Boolean clearTimes = false;
	private Boolean isWorking = false; 
	private IPreferenceStore store;
	
	public TimeMeter(List<Storage> storageList, Map<String, List<Integer>> speedMap, TimeMeterView view) {
		store = StorageManagementActivator.getDefault().getPreferenceStore();
		this.storageList = storageList;
		this.speedMap = speedMap;
		this.view = view;
		this.TEST_FILE_SIZE = store.getInt(PreferenceConstants.P_TEST_FILE_SIZE) * 1024;
	}
	
	@Override
	public void run() {
		String fileName = dirName + "/unicore_storage_test_" + new Date().getTime();
		Random random = new Random(); 
		byte[] data = new byte[TEST_FILE_SIZE];
		for (int i = 0; i < TEST_FILE_SIZE; i++) {
			data[i] = (byte) random.nextInt(256);
		}
		while (true) {
			try {
				checkStorageList(storageList);
			} catch (Exception e) {
				e.printStackTrace();
			}
			waitIfNeeded();
			for (Storage storage : myStorageList) {
				try {
	        removeFile(storage, dirName);
        } catch (Exception e) {
        	// DO NOTHINK
        	System.out.println("Fred remove exception");
        }
				try {
	        createDir(storage, dirName);
        } catch (Exception e) {
        	//storage.setAccessDenied(true);
        }
			}
			List<Storage> myStorageList2 = new ArrayList<Storage>(myStorageList);
			for (Storage storage : myStorageList) {
				myStorageList2.remove(storage);
				waitIfNeeded();
				System.out.println("a");
				synchronized (clearTimes) {
					if (clearTimes) {
						TEST_FILE_SIZE = store.getInt(PreferenceConstants.P_MEASURE_INTERVAL);
						clearTimes = false;
						speedMap.clear();
						break;
					}
				}
				try {
					if (storage.getAccessDenied() || !storage.checkConnection()) {
						System.out.println("Continue " + storage.getName());
						continue;
					} else {
						System.out.println("Connection OK " + storage.getName());
					}
				} catch (Throwable e) {
					if (e.getMessage().contains("Access denied")) {
						System.out.println("Access denied " + storage.getName());
						storage.setAccessDenied(true);
					}
					continue;
				}
				
				try {
					waitIfNeeded();
					transferTestFileToStorage(storage, fileName, data);
					waitIfNeeded();
					transferTestFileFromStorage(storage, fileName, data);
				} catch (Throwable e) {
					System.out.println(storage.getName());
					System.out.println("Throwed2 " + e.getClass() + " " + e.getMessage());
					System.out.println(e.getLocalizedMessage());
					e.printStackTrace();
				}
				
				// measure time between storages
				for (Storage storage2 : myStorageList2) {
					System.out.println("b");
					waitIfNeeded();
					try {
						if (storage2.getAccessDenied() || !storage2.checkConnection()) {
							storage2.getClient().getCurrentTime();
							System.out.println("Continue2 " + storage2.getName());
							continue;
						}
					} catch (Throwable e) {
						if (e.getMessage().contains("Access denied")) {
							System.out.println("Access denied2 " + storage2.getName());
							storage2.setAccessDenied(true);
						}
						continue;
					}
					waitIfNeeded();
					try {
						transferBetweenStorages(storage, storage2, fileName);
					} catch (Throwable e) {
						System.out.println("Throwed " + e.getClass() + " " + e.getMessage());
					}
					waitIfNeeded();
					try {
						transferBetweenStorages(storage2, storage, fileName);
					} catch (Throwable e) {
						System.out.println("Throwed " + e.getClass() + " " + e.getMessage());
					}
				}
			}
			
			if (view == null) {
				view = StorageManagementActivator.getDefault().getTimeMeterView();
			}
			if ((view != null) && (view.getTableViewer() != null)) {
				Display display = PlatformUI.getWorkbench().getDisplay();
				display.asyncExec(new Runnable() {
					@Override
					public void run() {
						view.refreshInput();
					}
				});
			}
			try {
				Thread.sleep(store.getInt(PreferenceConstants.P_MEASURE_INTERVAL) * 60000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	private void createDir(Storage storage, String dName) throws BaseFault {
	  storage.createDirectory("/" + dName);
  }

	private void removeFile(Storage storage, String fileName) throws BaseFault {
		storage.delete("/" + fileName);
	}

	private void checkStorageList(List<Storage> storageList) throws Exception {
		if (myStorageList == null) {
			myStorageList = new ArrayList<Storage>();
		}
		synchronized (storageList) {
			for (Storage s : storageList) {
				if (!myListContainsStorage(myStorageList, s)) {
					Storage s2 = new Storage(s.getUrl(), s.getName());
					s2.setDN(s.getDN());
					myStorageList.add(s2);
				}
			}
		}
	}

	private boolean myListContainsStorage(List<Storage> storageList, Storage s) {
		for (Storage s2 : storageList) {
			if (s2.getUrl().equals(s.getUrl())) {
				return true;
			}
		}
		return false;
	}
	
	private void addResult(String from, String to, Integer speed) {
		String key = from + " " + to;
		System.out.println("Res " + key + " " + speed);
		List<Integer> speedList = speedMap.get(key);
		if (speedList == null) {
			speedList = new ArrayList<Integer>();
			speedList.add(speed);
			speedMap.put(key, speedList);
		} else {
			if (speedList.size() >= store.getInt(PreferenceConstants.P_TIMES_HISTORY_SIZE)) {
				speedList.remove(0);
			}
			speedList.add(speed);
		}
	}
	
	public void fireTestFileSizeChanged() {
		synchronized (clearTimes) {
			clearTimes = true;
		}
	}
	
	private void transferTestFileToStorage(Storage storage, String fileName, byte[] data) throws Exception {
		FileTransferClient ftClient = null;
		try {
			ProtocolType.Enum protocol = StorageTools.getProtocol(data.length, storage.getSupportedProtocols());
			System.out.println("Protocol " + protocol + " - " + storage.getName());
			Map<String,String> extraParameters = new HashMap<String, String>();
			StorageTools.provideParameters(extraParameters, protocol.toString());
			FileTransferClient transferClient = storage.getClient().getImport("/" + fileName, false, protocol, extraParameters);
			
			MonitoredInputStream monitoredInStream = new MonitoredInputStream(new ByteArrayInputStream(data));
			transferClient.writeAllData(new BufferedInputStream(monitoredInStream));
			addResult("localhost", storage.getUrl(), (int) (monitoredInStream.getTransferTime()));
		} finally {
			if (ftClient != null) {
				try {
					ftClient.destroy();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	private void transferTestFileFromStorage(Storage storage, String fileName, byte[] data) throws Exception {
		FileTransferClient ftClient = null;
		try {

			ProtocolType.Enum protocol = StorageTools.getProtocol(data.length, storage.getClient().getSupportedProtocols());
			System.out.println("Protocol " + protocol + " - " + storage.getName());
			Map<String, String> extraParameters = new HashMap<String, String>();
			StorageTools.provideParameters(extraParameters, protocol.toString());
			storage.getClient().getExport(URIUtil.encode("/" + fileName, org.apache.commons.httpclient.URI.allowed_fragment),
					protocol, extraParameters);
			ftClient = storage.getExport(URIUtil.encode("/" + fileName, org.apache.commons.httpclient.URI.allowed_fragment));

			MonitoredOutputStream monitoredOutStream = new MonitoredOutputStream(new ByteArrayOutputStream(TEST_FILE_SIZE));
			ftClient.readAllData(new BufferedOutputStream(monitoredOutStream));
			addResult(storage.getUrl(), "localhost", (int) (monitoredOutStream.getTransferTime()));
		} finally {
			if (ftClient != null) {
				try {
					ftClient.destroy();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	private void transferBetweenStorages(Storage srcStorage, Storage dstStorage, String fileName) throws Exception {
		TransferControllerClient tcc = null;
		try {   
			EndpointReferenceType dstEPR = dstStorage.getClient().getEPR();
			String descDstSMS = dstStorage.getUrl();
					           
			String dstDN = dstStorage.getDN();
			if (dstDN == null) {
				return;
			}
			IUASSecurityProperties securityProperties = IdentityActivator.getDefault().getUASSecProps(dstEPR.getAddress().getStringValue());
			securityProperties.getETDSettings().setReceiver(new X500Principal(dstDN));		           
			securityProperties.getETDSettings().setExtendTrustDelegation(true);
			
			StorageClient dstSMS = null;
			try {
				dstSMS = new StorageClient(descDstSMS, dstEPR, securityProperties);
			} catch (Exception e) {
				e.printStackTrace();
			}
			
			String protocol = StorageTools.getProtocol(TEST_FILE_SIZE, dstStorage.getSupportedProtocols(), srcStorage.getSupportedProtocols()).toString();
			Map<String, String> extraParameters = new HashMap<String, String>();
			String srcU6URI = protocol + ":" + srcStorage.getUrl() + "#/" + fileName;
			
			Long startTime = null;
			try {
				startTime = new Date().getTime();
				tcc = dstSMS.fetchFile(srcU6URI, fileName, extraParameters);
			} catch (Exception e) {
				e.printStackTrace();
			}
			if (tcc != null) {
				while(!tcc.isComplete() && !tcc.getStatus().startsWith("FAILED")) {
					Thread.sleep(10);
				}
				System.out.println("tcc status " + tcc.getStatus());
				if (tcc.getStatus().startsWith("DONE")) {
					long endTime = new Date().getTime();
					addResult(srcStorage.getUrl(), dstStorage.getUrl(), (int) (endTime - startTime));
				}
			} else {
				System.err.println("tcc is null");
			}
		} finally {
			if (tcc != null) {
				try {
					tcc.destroy();
				} catch (Exception e) {
					e.printStackTrace();
				} 
			}
		}
	}
	
	private void waitIfNeeded() {
		boolean localIsWorking;
		
		synchronized (isWorking) {
			localIsWorking = isWorking();	
		}
		
		while (!localIsWorking) {	
			try {
				Thread.sleep(360000);
			} catch (InterruptedException e) {
				// do nothing
			}
			synchronized (isWorking) {
				localIsWorking = isWorking();	
			}
		}
	}

	public void setWorking(boolean b) {
		synchronized (isWorking) {
			isWorking = b;
		}
	}
	
	public boolean isWorking() {
		synchronized (isWorking) {
			return isWorking;
		}
	}
	
}
